package simpleTaskDispatcher.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;

import simpleTaskDispatcher.model.enums.DispatchingEvent;
import simpleTaskDispatcher.model.tasks.ITask;
import simpleTaskDispatcher.model.tasks.impl.ReleaseWorkerTask;
import tradingStrategyDeveloper.api.model.utils.SequentialIDGenerator;

public class Worker extends Thread {

	private static SequentialIDGenerator gen = new SequentialIDGenerator();
	
	TaskQueue assignedTaskQueue;
	private Semaphore assignedTaskQueueSemaphore = new Semaphore(0);
	
	private WorkersPool workersPool;
	
	private String currentContextID;
	private Map<String, Object> context = new HashMap<String, Object>();
	
	private final short id;
	
	private boolean alive = true;
	
	private Semaphore terminatedPoolWorkers;
	
	public Worker(WorkersPool workersPool, Semaphore terminatedPoolWorkers) {
		super();
		this.workersPool = workersPool;
		this.terminatedPoolWorkers = terminatedPoolWorkers;
		this.id = gen.nextShortID();
		setStatus(null);
	}
	
	public void setStatus(String status) {
		if (status == null) {
			this.setName("Worker " + id);	
		} else {
			this.setName("Worker " + id + " (" + status + ")");
		}
	}
	
	@Override
	public void run() {
		onNewWorker(this);
		setStatus("started");
		try {
			assignedTaskQueue = null;
			setStatus("started, signaling creation");
			workersPool.release(this, DispatchingEvent.NEW_WORKER);
			
			setStatus("started, beginning normal cycle");
			while(alive) {
				setStatus("Waiting for a new task queue");
				
				Semaphore assignedTaskQueueSemaphoreUnsynchronized;
				synchronized (this) {
					assignedTaskQueueSemaphoreUnsynchronized = assignedTaskQueueSemaphore;
				}
				if (assignedTaskQueueSemaphoreUnsynchronized != null) {
					assignedTaskQueueSemaphoreUnsynchronized.acquire();
				}

				setStatus(null);
				 if (assignedTaskQueue == null) {
					 alive = false;
					 break;
				 }
				
				//Working with this assigned task queue
				setStatus("New task queue assigned");
				WORKING_ON_TASK_FEED: while(alive) {

					setStatus("Waiting for a new task");
					ITask task = assignedTaskQueue.take();
					
					String taskName = task.getClass().getSimpleName() + "#" + task.getUniqueID();
					
					if (task instanceof ReleaseWorkerTask) {
						ReleaseWorkerTask releaseTask = (ReleaseWorkerTask) task;
						TaskQueue lastAssignedTaskQueue = assignedTaskQueue;
						
						setStatus("Releasing task queue " + lastAssignedTaskQueue.getName());
						assignedTaskQueue = null;
						
						
						setStatus("Releasing task queue " + lastAssignedTaskQueue.getName() + ", signaling worker pool");
						workersPool.release(this, releaseTask.getEvent());
						task.setWorker(this);
						task.setTaskQueue(assignedTaskQueue);
						task.execute();

						setStatus("Task queue " + lastAssignedTaskQueue.getName() + " released");
						afterTaskQueueRelease(lastAssignedTaskQueue);
						break WORKING_ON_TASK_FEED;
						
					} else {
						//If 
						if (task.getContextID() != null && (currentContextID == null || !task.getContextID().equals(currentContextID))) {
							setStatus("Loading task context for " + taskName);
							currentContextID = task.getContextID();
							context.clear();
							task.loadContext(this);
						}
						setStatus("Executing task " + taskName);
						task.setTaskQueue(assignedTaskQueue);
						task.setWorker(this);
						task.execute();
						task.markExecuted();
						assignedTaskQueue.onTaskFinished(task);
						
						setStatus("Finished task " + taskName);
					}
					assignedTaskQueue.finished(task);
				}
				
				setStatus("Dying");
			}
			
		} catch (InterruptedException e) {
			
			setStatus("Interrupted");
			onCloseWorker(this);
			throw new RuntimeException(e);
		}
		
		setStatus("Dying, really!");
		terminatedPoolWorkers.release();
		if (assignedTaskQueue != null) {
			setStatus("Died");
			onCloseWorker(this);
			afterTaskQueueRelease(assignedTaskQueue);
			assignedTaskQueue = null;
		} else {

			setStatus("Died");
			onCloseWorker(this);
		}	
	}
	
	
	public synchronized void assignToTaskQueue(TaskQueue taskQueue) {
		if (this.assignedTaskQueue != null) {
			throw new IllegalStateException("Already assigned to a task queue");
		}
		if (!alive) {
			throw new IllegalStateException("This worker is marked for termination, it cannot be assigned to a task queue");
		}
		
		this.assignedTaskQueue = taskQueue;
		assignedTaskQueueSemaphore.release(); //0  or 1
	}
	
	synchronized void close() {
		if (alive) {
			alive = false;
			if (this.assignedTaskQueue == null) {
				//This worker is waiting for a new task queue
				assignedTaskQueueSemaphore.release();
			}
			assignedTaskQueueSemaphore = null;
		}
	}
	
	public synchronized Worker set(String key, Object value) {
		context.put(key, value);
		return this;
	}
	
	@SuppressWarnings("unchecked")
	public synchronized <T> T get(String key) {
		return (T) context.get(key);
	}
	
	public synchronized boolean isWorkerAlive() {
		return alive;
	}
	
	private void afterTaskQueueRelease(TaskQueue queue) {
		setStatus("Released " + queue.getName());
		queue.getTerminatedTaskQueueWorkers().release();
	}
	
	@Override
	public boolean equals(Object obj) {
		Worker w = (Worker) obj;
		return this.id == w.id;
	}

	
	private static List<Worker> workers = new ArrayList<Worker>();
	private static synchronized void onNewWorker(Worker worker) {
		workers.add(worker);
	}
	
	private static synchronized void onCloseWorker(Worker worker) {
		workers.remove(worker);
	}
	
	public static synchronized List<Worker> getAllRunningWorkers() {
		List<Worker> copy = new ArrayList<Worker>();
		copy.addAll(workers);
		return copy;
	}
}
